﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using VsxFactory.Modeling.VisualStudio;
using System.IO;
using System.ComponentModel;

namespace VsxFactory.Modeling.Strategies.Rules
{
    /// <summary>
    /// This rule must be used to generate a node (file) in the solution explorer.
    /// </summary>
    /// <remarks>
    /// 
    /// </remarks>
    public class NodeGenerationRule : GenerationRule 
    {
        public enum GenerationAction
        {
            Nothing,
            Generate,
            Remove,
            Rename
        }

        public IVsProjectFileItem Node { get; private set; }
        private bool _generatorsInitialized;

        public NodeGenerationRule() : this(null)
        {
        }

        public NodeGenerationRule(Type melType=null, string outputFileNameFormat = "{Name}.cs") : base(melType)
        {
            OverwriteFile = true;
            OutputFileNameFormat = outputFileNameFormat;
        }

        protected virtual string CalculateFileName(IVsProject project, GenerationSpec element, string name = null)
        {
            System.Diagnostics.Contracts.Contract.Requires(project != null);
            System.Diagnostics.Contracts.Contract.Requires(element != null);

            ExpressionEvaluator evaluator = new ExpressionEvaluator(element.ModelElement, name);
            string result = evaluator.Evaluate(OutputFileNameFormat);
            return result.Replace("{Name}", Utils.NormalizeName(name ?? element.Name));
        }

        public override void Remove(IStrategyContext context, GenerationSpec targetElement)
        {
            var project = GetProjectNode(context, targetElement);
            if (project != null)
            {
                string fullName = Path.Combine(project.Path, CalculateFileName(project, targetElement));
                Node = project.FindByPath<IVsProjectFileItem>(fullName);
                if (Node == null)
                    return;
                Node.Remove();
            }
        }

        public override void Change(IStrategyContext context, GenerationSpec candidateElement, GenerationSpec targetElement, Guid propertyId, object oldValue)
        {
            IVsProject project = GetProjectNode(context, targetElement);
            if (project == null)
                return;

            var action = GetActionOnChange(context, candidateElement, propertyId, oldValue);
            string oldName = oldValue as string;

            switch (action)
            {
                case GenerationAction.Generate:
                    Generate(context, targetElement, null);
                    break;
                case GenerationAction.Remove:
                    string fullName = Path.Combine(project.Path, CalculateFileName(project, targetElement, oldName));
                    Node = project.FindByPath<IVsProjectFileItem>(fullName);
                    if (Node == null)
                    {
                        Generate(context, targetElement, null);
                        return;
                    }
                    Node.Remove();
                    break;
                case GenerationAction.Rename:
                    Rename(project, targetElement, oldName);
                    break;
                case GenerationAction.Nothing:
                    break;
            }
        }

        protected virtual void Rename(IVsProject project, GenerationSpec targetElement, string oldName)
        {
            project.RenameItem(CalculateFileName(project, targetElement, oldName), CalculateFileName(project, targetElement));
        }

        protected virtual GenerationAction GetActionOnChange(IStrategyContext context, GenerationSpec element, Guid propertyId, object oldValue)
        {
            if (element == null)
                return GenerationAction.Nothing;
            if (propertyId == element.NamePropertyId)
                return !String.IsNullOrEmpty(oldValue as string) ? GenerationAction.Rename : GenerationAction.Nothing;

            return GenerationAction.Generate;
        }

        public override IVsProjectFileItem GetCurrentNode(IStrategyContext context, GenerationSpec targetElement)
        {
            var project = GetProjectNode(context, targetElement);
            if (project == null)
                return null;

            string relativeFileName = CalculateFileName(project, targetElement);
            string fullName = Path.Combine(project.Path, relativeFileName);
            var node = project.FindByPath<IVsProjectFileItem>(fullName);
            if (node == null)
                node = OnOutputFileDoesntExist(project, fullName);
            return node;
        }

        protected virtual IVsProjectFileItem OnOutputFileDoesntExist(IVsProject project, string fullName)
        {
            return null;
        }

        public override bool Generate(IStrategyContext context, GenerationSpec targetElement, IVsProjectFileItem node)
        {
            InitializeGenerators(context);

            var project = GetProjectNode(context, targetElement);
            if (project == null)
            {
                return false;
            }

            // Les generateurs aprés le 1er permettent de générer les nested files
            bool primaryNode = true;

            string relativeFileName = CalculateFileName(project, targetElement);
            string fullName = Path.Combine(project.Path, relativeFileName);
            if (node != null)
            {
                Node = node;
                primaryNode = false;
            }
            else
            {
                Node = project.FindByPath<IVsProjectFileItem>(fullName);
                if (Node != null && !OverwriteFile)
                    return true;
            }

            foreach (var generator in Generators)
            {
                if (generator.Generate(context, ref fullName, targetElement, null))
                {
                    if (Node == null || !primaryNode)
                        Node = generator.AddToSolution(project, Node, fullName);
                }
                else
                {
                    if (Node == null)
                    {
                        try { project.RemoveItem(relativeFileName); }
                        catch { }
                        return false;
                    }
                }
                primaryNode = false;
            }
            return true;
        }

        private void InitializeGenerators(IStrategyContext context)
        {
            if (_generatorsInitialized)
                return;
            Generators.ForEach(g => g.Initialize(context));
            _generatorsInitialized = true;
        }

        private IVsProject GetProjectNode(IStrategyContext context, GenerationSpec targetElement)
        {
            if (ProjectSelector != null)
            {
                var pn = ProjectSelector.GetProjectNode(context, targetElement);
                if (pn != null)
                {
                    return pn;
                }
            }
            return targetElement.GenerationProject;
        }

        [DefaultValue(null)]
        public IProjectSelector ProjectSelector { get; set; }
    }

}
